<?php

class UserSession
{
  public $sessionId;
  public $timestampCreated;
  public $lastActivity;
  public $userId;
  public $userRemoteIp;
  public $userAgentHash;

  public $isUserLoggedIn = false;

  public function __construct()
  {    
    $this->Init();
    $this->InitializeSession();
  }

  /**
   * Check current user's session
   * @return <type>
   */
  private function InitializeSession()
  {
    $session_id = UserSession::GetUserSessionId();
    
    if (!empty($session_id) && $this->LoadData($session_id))
    {
      $this->lastActivity = 0;
      if (!empty($_SESSION['LAST_ACTIVITY']))
        $this->lastActivity = $_SESSION['LAST_ACTIVITY'];
      
      // validate session
      if ($this->ValidateSessionData($session_id))
      {
        $this->isUserLoggedIn = true;
        $_SESSION['LAST_ACTIVITY'] = time();
      }
      else
      {
        $this->Delete();
        $this->Init();
      }
    }

    return $this->isUserLoggedIn;
  }

  private function Init()
  {
    $this->sessionId = '';
    $this->userId = -1;
    $this->lastActivity = 0;
    $this->timestampCreated = 0;
    $this->isUserLoggedIn = false;
    $this->userAgentHash = '';
    $this->userRemoteIp = '';
  }

  /**
   * Query DB for session data and loads it into model
   * @param string $session_id session id to be loaded
   * @return bool True if loaded sucessfully, otherwise false.
   */
  private function LoadData($session_id = null)
  {
    $where = 1;
    if (!empty($session_id))
      $where .= ' AND session_id=' . ToSqlQuotedString($session_id);

    $rows = SelectRow('sessions', 'user_id, session_id, timestamp, user_ip, user_agent_hash', $where);
    if (count($rows) !== 1) return false;
    $data = $rows[0];

    $this->sessionId = $data['session_id'];
    $this->userId = $data['user_id'];
    $this->timestampCreated = strtotime($data['timestamp']);
    $this->userRemoteIp = $data['user_ip'];
    $this->userAgentHash = $data['user_agent_hash'];
    
    return true;
  }

  /**
   *
   * @uses SESSION_CHECK_IP
   * @uses SESSION_CHECK_AGENT
   * @param <type> $session_id
   * @return <type>
   */
  private function ValidateSessionData()
  {
    if (empty($this->sessionId))
      return false;
    // NOTE: add constant settings if want to perform these checks
    if (defined('SESSION_TIMEOUT') && !empty($this->timestampCreated))
    {
      // check user session expiration time
      if ($this->timestampCreated + SESSION_TIMEOUT < time())
        return false;
    }
    if (defined('SESSION_INACTIVE_TIMEOUT') && !empty($this->lastActivity))
    {
      // check user last activity expiration
      if ($this->lastActivity + SESSION_INACTIVE_TIMEOUT < time())
        return false;
    }
    if (defined('SESSION_CHECK_IP') && SESSION_CHECK_IP)
    {
      // check user remote ip address
      if (strcmp($_SERVER['REMOTE_ADDR'], $this->userRemoteIp) !== 0)
        return false;
    }
    if (defined('SESSION_CHECK_AGENT') && SESSION_CHECK_AGENT)
    {
      // check user agent hash
      if (strcmp(md5($_SERVER['HTTP_USER_AGENT']), $this->userAgentHash) !== 0)
        return false;
    }

    return true;
  }

  /**
   * Saves in DB sessions table the session data by replacing user_id if such exists 
   * @return bool True if operation is successful, otherwise false
   */
  public function Save()
  {
    if (empty($this->sessionId) || empty($this->userId)) return false;

    $values = array(
      'session_id' => $this->sessionId,
      'user_id' => $this->userId,
      'timestamp' => $this->timestampCreated,
      'user_ip' => $this->userRemoteIp,
      'user_agent_hash' => $this->userAgentHash
    );

    return ReplaceRow('sessions', $values);
  }

  /**
   * Delete record from DB with current session
   * @return bool True if operation is successul, otherwise false.
   */
  private function Delete()
  {
    if (empty($this->sessionId)) return false;

    $where = 'session_id=' . ToSqlQuotedString($this->sessionId);
    return DeleteRow('sessions', $where);
  }


  /**
   * Logs in user by validating it's credentials and creating unique session and
   *     saving it to database and cookies.
   * Note: Also deletes expired sessions from database
   *
   * @param int $user_id
   * @return <type>
   */
  public function LoginUser($email, $password)
  {
    $this->isUserLoggedIn = false;
    $user = new User(null, $email);
    if ($user->id < 0)
      return false;
    if (!User::ValidateCredentials($email, $password))
      return false;

    // if valid credentials reset session.
    $this->Delete();
    $this->Init();

    // Delete expired sessions from database
    UserSession::CleanUpDB();
    
    do
    {
      // create unique session id
      $this->sessionId = md5(microtime(true) + $user->id);
    } while (UserSession::Exists($this->sessionId));

    $time = time();
    $this->timestampCreated = date('Y-m-d H:i:s', $time);
    $_SESSION['LAST_ACTIVITY'] = $time;
    $this->userAgentHash = md5($_SERVER['HTTP_USER_AGENT']);
    $this->userRemoteIp = $_SERVER['REMOTE_ADDR'];
    $this->userId = $user->id;

    $res = $this->Save();
    $res = $res && UserSession::WriteUserSessionCookie($this->sessionId);
    $this->isUserLoggedIn = $res;

    return $this->sessionId;
  }

  /**
   * Unset user session and delete record from DB
   */
  public function LogoutUser()
  {
    unset($_COOKIE[SESSION_COOKIE_NAME]);
    unset($_SESSION['LAST_ACTIVITY']);
    $this->Delete();
    $this->Init();
  }
  

  /**
   * Checks if specified session id already exists in database
   * @param <type> $session_id
   */
  public static function Exists($session_id)
  {
    $session_id = trim($session_id);
    if (!empty($session_id))
      return RowExists('sessions', 'session_id='.ToSqlQuotedString($session_id));

    return false;
  }

  /**
   * Looks for session_id in $_COOKIE
   * @return string session id if found, otherwise empty string
   */
  private static function GetUserSessionId()
  {
    $user_session_id = '';

    if (defined('SESSION_COOKIE_NAME') && isset($_COOKIE[SESSION_COOKIE_NAME]))
      $user_session_id = $_COOKIE[SESSION_COOKIE_NAME];
    
    return $user_session_id;
  }
  
  /**
   * Cleans up expired sessions from database
   * @param int $userId specify user id if want to expire session of a specific user
   */
  public static function CleanUpDB($userId = null)
  {
    $where = null;
    if (!empty($userId))
      $where = 'user_id=' . ToSqlQuotedString($userId);
    else if (defined('SESSION_TIMEOUT'))
    {
      $lifetime = intval(constant('SESSION_TIMEOUT'));
      if ($lifetime > 0) {
        $where = 'timestamp<' . ToSqlQuotedString(date('Y-m-d H:i:s', (time() - $lifetime)));
      }
    }
    if (isset($where))
      return DeleteRow('sessions', $where);
    return true;
  }

  /**
   * Set HTTP response header which is sent to client to write cookie with
   * key SESSION_COOKIE_NAME and session id
   *
   * @uses setcookie php built in function
   * @param <type> $session_id
   * @return True if opearation was successful, otherwise false
   */
  private static function WriteUserSessionCookie($session_id = null)
  {
    if (!empty($session_id) && defined('SESSION_COOKIE_NAME')
      && function_exists('setcookie'))
    {
      if (@constant('COOKIE_EXPIRATION_TIMEOUT') == 0)
        $expires = 0;
      else
        $expires = time() + @constant('COOKIE_EXPIRATION_TIMEOUT');

      // set cookie to expire at session end
      if (@constant('IS_DEVELOPMENT_SERVER')) // localhost
        return setcookie(SESSION_COOKIE_NAME, $session_id, $expires, '/');
      else
        return setcookie(SESSION_COOKIE_NAME, $session_id, $expires, '/', SESSION_COOKIE_DOMAIN_NAME); // www.allprices.md
    }
    return false;
  }
  
}
?>